// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ripple

import (
	"log"
	"math/rand"
	"sync"
	"time"

	"circuit/kit/rh"
)

// lockFID
type lockFID struct {
	rh.AnchoredFID
	//
	q   rh.Q
	gtm time.Time
	sem *Semaphore
	sync.Mutex
	transaction
}

func NewLock(sem *Semaphore) rh.FID {
	return &lockFID{
		sem: sem,
		gtm: time.Now(),
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
	}
}

func (fid *lockFID) String() string {
	return "lock"
}

func (fid *lockFID) Q() rh.Q {
	return fid.q
}

func (fid *lockFID) Open(flag rh.Flag, intr rh.Intr) (err error) {
	fid.Lock()
	defer fid.Unlock()
	//
	if flag.Create || flag.Truncate || flag.Deny /* avoid blocking simulated opens from the fuse/rh setattr hack */ {
		return rh.ErrPerm
	}
	//
	fid.Lock()
	defer fid.Unlock()
	//
	fid.transaction = fid.sem.NewTx()
	if fid.transaction.Wait(intr) != nil { // Block open until lock obtained
		return rh.ErrGone
	}
	return nil
}

func (fid *lockFID) Read(_ int64, count int, intr rh.Intr) (chunk rh.Chunk, err error) {
	defer func() {
		log.Printf("read %v", err)
	}()
	//
	fid.Lock()
	defer fid.Unlock()
	//
	if fid.transaction == nil {
		return nil, rh.ErrGone
	}
	if err = fid.transaction.Wait(intr); err != nil { // Continue with /dev/null-style write, only if lock held
		log.Printf("read unwait %v", err)
		return 0, rh.ErrGone
	}
	return 0, nil
}

func (fid *lockFID) Write(offset int64, data rh.Chunk, intr rh.Intr) (n int, err error) {
	defer func() {
		log.Printf("write %v", err)
	}()
	//
	fid.Lock()
	defer fid.Unlock()
	//
	if fid.transaction == nil {
		return 0, rh.ErrGone
	}
	if err = fid.transaction.Wait(intr); err != nil { // Continue with /dev/null-style write, only if lock held
		log.Printf("write unwait %v", err)
		return 0, rh.ErrGone
	}
	if data == nil {
		data = (rh.ByteChunk)(nil)
	}
	return len(data.(rh.ByteChunk)), nil
}

func (fid *lockFID) Clunk() {
	fid.Lock()
	defer fid.Unlock()
	//
	if fid.transaction != nil {
		fid.transaction.Clunk()
	}
	fid.transaction = nil
}

func (fid *lockFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) > 0 {
		return nil, rh.ErrClash
	}
	return NewLock(fid.sem), nil
}

func (fid *lockFID) Stat() (*rh.Dir, error) {
	return &rh.Dir{
		Q:      fid.q,
		Mode:   rh.Mode{Attr: rh.ModeIO},
		Perm:   0666, // wr-wr-wr-, acts as /dev/null for writing and empty file for reading, when lock acquired
		Atime:  fid.gtm,
		Mtime:  fid.gtm,
		Name:   "lock",
		Length: 0,
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}
